package com.kamistoat.netty.niochat;

import sun.awt.X11.XSystemTrayPeer;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO群聊服务端
 */
public class NIOChatServer {
    // 定义属性--selector/ServerSocketChannel
    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final int PORT = 6667;

    // 构造器，初始化上述的几个属性，直接将顶层的ServerSocketChannel注册到Selector中
    public NIOChatServer() {
        try {
            this.selector = Selector.open();
            this.listenChannel = ServerSocketChannel.open();
            this.listenChannel.socket().bind(new InetSocketAddress(PORT));
            this.listenChannel.configureBlocking(false);
            this.listenChannel.register(this.selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Selector使用select方法监听所有注册Channel上的事件
     */
    public void listen() {
        try {
            // 在一个无限循环中监听
            while (true) {
                // 阻塞两秒，搜集事件，当然此时服务端没有其他业务，一直阻塞也可以
                int count = this.selector.select();
                if (count > 0) {
                    Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    // 遍历事件
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        if (selectionKey.isAcceptable()) {
                            // 如果是新连接事件
                            SocketChannel socketChannel = this.listenChannel.accept();
                            socketChannel.configureBlocking(false);
                            socketChannel.register(this.selector, SelectionKey.OP_READ);
                            System.out.println(socketChannel.getRemoteAddress() + "上线");
                        } else if (selectionKey.isReadable()) {
                            // 如果某个客户端连接发送消息，使其对应的服务端SocketChannel变为读就绪
                            readData(selectionKey);
                        }
                        iterator.remove();
                    }
                } else {
                    System.out.println("等待中..");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
    }

    /**
     * 处理 读就绪 事件
     *
     * @param selectionKey Selector监听到的事件的SelectionKey
     */
    private void readData(SelectionKey selectionKey) {
        SocketChannel socketChannel = null;
        try {
            // 取得关联的channel
            socketChannel = (SocketChannel) selectionKey.channel();
            // 创建Buffer，并读取SocketChannel中的数据到 buffer中
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int count = 1;
            while(count > 0){
                count = socketChannel.read(byteBuffer);
                if (count > 0) {
                    // 接收从某个客户端发送来的消息
                    String msg = new String(byteBuffer.array());
                    System.out.println("form " + socketChannel.getRemoteAddress() + ":" + msg);
                    // 将该消息转发给其他客户端
                    sendInfoToOtherClients(msg, socketChannel);
                }
            }
        } catch (IOException e) {
            try {
                // 如果在上面read的过程中客户端关闭连接，则会抛出异常被这里捕获
                System.out.println(socketChannel.getRemoteAddress() + "离线了...");
                // 取消该SocketChannel在Selector中的注册信息
                selectionKey.cancel();
                // 关闭通道
                socketChannel.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

    /**
     * 消息转发给其他Channel
     *
     * @param msg
     * @param selfSocketChannel 自身SocketChannel，向除自身和ServerSocketChannel外的其他SocketChannel转发消息
     */
    private void sendInfoToOtherClients(String msg, SocketChannel selfSocketChannel) {
        for (SelectionKey selectionKey : this.selector.keys()) {
            // 由于是遍历，所以并不确定Channel的类型，不能在这里强转
            Channel tempChannel = selectionKey.channel();
            // 首先通过类型排除掉 ServerSocketChannel，然后排除掉自身
            if (tempChannel instanceof SocketChannel && !tempChannel.equals(selfSocketChannel)) {
                // 此时可以确定，一定是 SocketChannel了
                SocketChannel tarChannel = (SocketChannel) tempChannel;
                // 创建服务端buffer，向buffer写入数据后，将buffer写入到channel中
                ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
                try {
                    tarChannel.write(byteBuffer);
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
        System.out.println("服务器转发消息完成");
    }

    /**
     * 启动服务端
     * @param args
     */
    public static void main(String[] args) {
        NIOChatServer nioChatServer = new NIOChatServer();
        nioChatServer.listen();
    }
}
